Shell脚本实战09-MySQL主从复制异常监控

1. 需求

开发一个守护进程脚本,每30秒监控一次MySQL主从复制是否异常(包括不同步及延迟),如果有异常,则发送短信报警,并发送邮件给管理员存档。

2. 实验准备

2.1. 测试数据模拟

如果没有主从复制的环境,可以把下面的命令执行的结果部分的文本放到文件里读取来模拟主从复制的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
mysql> show slave status\G; #<==在主从复制的环境里可通过这个命令来查看状态
*************************** 1. row ***************************
Slave_IO_State: Connecting to master
Master_Host: 127.0.0.1
Master_User: repl
Master_Port: 3307
Connect_Retry: 60
Master_Log_File: aminglinux2.000002
Read_Master_Log_Pos: 4
Relay_Log_File: localhost-relay-bin.000006
Relay_Log_Pos: 4
Relay_Master_Log_File: aminglinux2.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB: mysql
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 4
Relay_Log_Space: 120
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:

2.2. 解题思路

  1. 判断主从复制是否异常,主要是检测如下参数对应的值:

    1
    2
    3
    Slave_IO_Running: Yes #<==IO线程状态必须为Yes
    Slave_SQL_Running: Yes #<==SQL线程状态必须为Yes
    Seconds_Behind_Master: 0 #<==和主库比较同步延迟的描述,这个参数很重要
  2. 读取状态数据或状态文件,然后取出对应的值,和正确的值进行比对,如果不符合,则表示存在故障,即调用报警脚本报警。

  3. 如果想要更专业,还可以在主从不同步时,查看相应的错误号,判断对应的错误号以自动修复主从复制故障(也可以用过在配置文件里的配置参数来实现自动忽略故障)。

3. 实现脚本

这个脚本是在企业生产中正式使用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/bin/bash
path=/server/scripts #<==定义脚本存放路径,请注意这个规范
MAIL_GROUP="test1@qq.com test2@qq.com" #<==邮件列表,以空格隔开
PAGER_GROUP="13400000001 13400000002" #<==手机列表,以空格隔开
LOG_FILE="/tmp/web_check.log" #<==日志路径
USER=root #<==数据库用户
PASSWORD=123456 #<==用户密码
PORT=3307 #<==端口
MYSQLCMD="mysql -u$USER -p$PASSWORD -S /data/$PORT/mysql.sock"
#<==登陆数据库命令
error=(1008 1007 1062) #<==可以忽略的主从复制错误号
RETVAL=0
[ ! -d "$path" ] && mkdir -p $path
function JudgeError(){ #<==定义判断主从复制错误的函数
for((i=0;i<${#error[*]};i++))
do
if [ "$1" == "${error[$i]}" ] #<==如果传入的错误号和数组里的元素相匹配,则执行then后面的命令
then
echo "MySQL slave errorno is $1, auto repairing it."
$MYSQLCMD -e "stop slave;set global sql_slave_skip_counter=1;start slave;" #<==自动修复
fi
done
return $1
}
function CheckDb(){ #<==定义检查数据库主从复制状态的函数
status=($(awk -F ':' '/_Running|Last_Errno|_Behend/{print $NF}' slave.log))
expr ${status[3]} + 1 &>/dev/null #<==这个是延迟状态值,用于进行是否为数字的判断
if [ $? -ne 0 ];then
status[3]=300 #<==如果不为数字,赋值300,延迟这个状态值可能是NULL,即非数字
fi
if [ "${status[0]}" == "Yes" -a "${status[1]}" == "Yes" -a ${status[3]} -lt 120 ]
#<==两个线程都为Yes,并且延迟小于120秒,即认为复制状态是正常的。
then
echo "MySQL slave status is ok."
return 0
else
echo "MySQL replcation is failed."
JudgeError ${status[2]}
#<==否则,将错误号${status[2}传入JudgeError函数,判断错误号是否可以自动修复
fi
}
function MAIL(){ #<==定义邮件函数
local SUBJECT_CONTENT=$1 #<==将函数的第一个传参赋给主题变量
for MAIL_USER in `echo $MAIL_GROUP` #<==遍历邮件列表
do
mail -s "$SUBJECT_CONTENT " $MAIL_USER < $LOG_FILE #<==发邮件
done
}
function PAGER(){ #<==定义手机函数
for PAGER_USER in `echo $PAGER_GROUP` #<==遍历手机列表
do
TITLE=$1 #<==将函数的第一个传参赋值给主题变量
CONTACT=$PAGER_USER #<==将手机号复制给CONTACT变量
HTTPGW=http://发送短信的地址
#<==发送短信地址,这个地址需要用户购买,如果想要免费的,就得用微信替代了
#send_message method1
curl -d cdkey=5ADF-EFA -d password=PLDBOY -d phone=$CONTACT -d message="$TITLE[$2]" $HTTPGW
#<==发送短信报警的命令。cdkey是购买短信网关时,由售卖者提供的,password是密码,也是由售卖者提供的
done
}
function SendMsg(){
if [ $1 -ne 0 ] #<==传入$1,如果不为0,则表示复制有问题,这里的$1即为CheckDB里的返回值(用检测失败的次数作为返回值,在后文执行主函数main时是通过调用SendMsg传参传进来的值)
then
RETVAL=1
NOW_TIME=`date +"%Y-%m-%d %H:%M:%S"` #<==报警时间
SUBJECT_CONTENT="mysql slave is error,errorno is $2,${NOW_TIME}." #<==报警主题
echo -e "$SUBJECT_CONTENT" | tee $LOG_FILE #<==输出信息,并记录到日志
MAIL $SUBJECT_CONTENT #<==法邮件报警,$SUBJECT_CONTENT作为函数参数传给MAIL函数体的$1,$NOW_TIME作为函数体传给$2
else
echo "MySQL slave status is ok."
RETVAL=0 #<==以0作为返回值
fi
return $RETVAL
}
function main(){
while true
do
CheckDb
SendMsg $? #<==传入第一个参数$?,即CheckDb里的返回值(用检测失败的次数作为返回值)
sleep 30
done
}
main

0%